package com.je.gateway.router.dispatcher;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.je.gateway.router.GlobalRouterParams;
import com.je.gateway.router.model.paramRouter.ParamUrl;
import com.netflix.config.DynamicPropertyFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.apache.servicecomb.edge.core.EdgeInvocation;
import org.apache.servicecomb.edge.core.Utils;
import org.apache.servicecomb.transport.rest.vertx.RestBodyHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * 请求参数路由器
 */
public class UrlParamsEdgeDispatcher extends GatewayAbstractRestDispatcher {

    private static final Logger logger = LoggerFactory.getLogger(UrlParamsEdgeDispatcher.class);

    private static final String KEY_ENABLED = "servicecomb.http.dispatcher.edge.paramRouter.enabled";

    public static final String CONFIGURATION_ITEM = "ParamRoutedConfigurationItem";

    public UrlParamsEdgeDispatcher() {
        if (this.enabled()) {
            loadConfigurations();
        }
    }

    @Override
    public int getOrder() {
        return 5;
    }

    @Override
    public void init(Router router) {
        String pattern = GlobalRouterParams.GLOBAL_PARAM_ROUTER.getPath();
        router.routeWithRegex(pattern).failureHandler(this::onFailure)
                .handler(createBodyHandler())
                .handler(this::preCheck)
                .handler(this::onRequest);
    }

    protected void preCheck(RoutingContext context) {
        List<ParamUrl> paramUrls = findConfigurationItems(context.request().path());
        if (paramUrls == null || paramUrls.isEmpty()) {
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.TRUE);
            context.next();
            return;
        }

        ParamUrl paramUrl = null;
        for (ParamUrl eachParamUrl : paramUrls) {
            //string类型获取
            if (eachParamUrl.getValueType().equals(ParamUrl.VALUE_TYPE_STRING) && checkStringParamUrl(context, eachParamUrl)) {
                paramUrl = eachParamUrl;
                break;
            }
            //array类型获取
            if (eachParamUrl.getValueType().equals(ParamUrl.VALUE_TYPE_ARRAY) && checkArrayParamUrl(context, eachParamUrl)) {
                paramUrl = eachParamUrl;
                break;
            }
        }

        if (paramUrl == null) {
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.TRUE);
            context.next();
            return;
        }

        context.put(CONFIGURATION_ITEM, paramUrl);
        context.next();
    }

    /**
     * 多参数路由检查，多参数路由必须所有参数都在一个服务中，否则路由失败
     *
     * @param context
     * @param paramUrl
     * @return
     */
    private boolean checkArrayParamUrl(RoutingContext context, ParamUrl paramUrl) {
        boolean pass = true;
        String paramValue = context.request().getParam(paramUrl.getParamKey());
        if (Strings.isNullOrEmpty(paramValue)) {
            pass = false;
            logger.error("The param router with the url {} can not route empty array value!", paramUrl.getPath());
            return pass;
        }

        String valueKey = paramUrl.getValueKey();
        List<String> paramValues = paramUrl.getParamValues();
        if (Strings.isNullOrEmpty(valueKey)) {
            //参数值格式a,b,c
            List<String> splitedArray = Splitter.on(",").splitToList(paramValue);
            for (String eachSplitedValue : splitedArray) {
                if (!paramValues.contains(eachSplitedValue)) {
                    pass = false;
                    paramUrl.setRouterValue(eachSplitedValue);
                    break;
                }
            }
        } else {
            //参数值格式[{ddCode:a},{ddCode:b}]
            JSONArray valueArray = JSON.parseArray(paramValue);
            for (int i = 0; i < valueArray.size(); i++) {
                if (!paramValues.contains(valueArray.getJSONObject(i).get(valueKey))) {
                    pass = false;
                    //增加此值是为日志和监控考虑
                    paramUrl.setRouterValue(valueArray.getJSONObject(i).getString(valueKey));
                    break;
                }
            }
        }
        return pass;
    }

    private boolean checkStringParamUrl(RoutingContext context, ParamUrl paramUrl) {
        boolean pass = false;
        List<String> paramValues = paramUrl.getParamValues();
        for (String eachParamValue : paramValues) {
            if (checkEqual(context, paramUrl, eachParamValue)) {
                pass = true;
                paramUrl.setRouterValue(eachParamValue);
                break;
            }
        }
        return pass;
    }

    private boolean checkEqual(RoutingContext context, ParamUrl paramUrl, String eachConfigValue) {
        String requestValue = context.request().getParam(paramUrl.getParamKey());
        String formValue = context.request().getFormAttribute(paramUrl.getParamKey());
        if (!paramUrl.isSubstr()) {
            return eachConfigValue.equals(requestValue) || eachConfigValue.equals(formValue);
        } else if (!Strings.isNullOrEmpty(requestValue)) {
            List<String> subList = Splitter.on(paramUrl.getSubChar()).splitToList(requestValue);
            return eachConfigValue.equals(subList.get(paramUrl.getSubIndex()));
        } else if (!Strings.isNullOrEmpty(formValue)) {
            List<String> subList = Splitter.on(paramUrl.getSubChar()).splitToList(formValue);
            return eachConfigValue.equals(subList.get(paramUrl.getSubIndex()));
        }
        return false;
    }

    protected void onRequest(RoutingContext context) {
        ParamUrl paramUrl = context.get(CONFIGURATION_ITEM);
        Boolean bypass = context.get(RestBodyHandler.BYPASS_BODY_HANDLER);
        if (Boolean.TRUE.equals(bypass)) {
            // clear flag
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.FALSE);
            context.next();
            return;
        }
        if (isGateWayPath(context, context.request().path())) {
            context.next();
            return;
        }
        if (paramUrl == null) {
            context.response().setStatusCode(404).end("error matching param url");
            return;
        }

        String path;
        if (!Strings.isNullOrEmpty(paramUrl.getRealPath())) {
            path = paramUrl.getRealPath();
        } else {
            path = Utils.findActualPath(context.request().path(), paramUrl.getPrefixSegmentCount());
        }

        EdgeInvocation edgeInvocation = createEdgeInvocation(context);
        if (paramUrl.getVersionRule() != null) {
            edgeInvocation.setVersionRule(paramUrl.getVersionRule());
        }

        edgeInvocation.init(paramUrl.getMicroServiceName(), context, path, httpServerFilters);
        logger.info("Matching with param dispatcher,the path {} pattern with {} is router to {} begin,the real path is {}! paramKey is {} paramValue is {}", context.request().path(), paramUrl.getPath(), paramUrl.getMicroServiceName(), path, paramUrl.getParamKey(), paramUrl.getRouterValue());
        edgeInvocation.edgeInvoke();

    }

    private List<ParamUrl> findConfigurationItems(String path) {
        List<ParamUrl> headerUrls = new ArrayList<>();
        for (ParamUrl item : GlobalRouterParams.GLOBAL_PARAM_ROUTER.getParamUrls()) {
            if (item.getPattern().matcher(path).matches()) {
                headerUrls.add(item);
            }
        }
        return headerUrls;
    }

    private void loadConfigurations() {
        GlobalRouterParams.loadUrlParamValues();
    }

    @Override
    public boolean enabled() {
        return DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_ENABLED, false).get();
    }

}
